// Zad1.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "Zad1.h"
#include "Point.h"
#include "Timer.h"
#include "String.h"

using std::stack;

#define MAX_LOADSTRING 100
#define POINT_STACK_WAIT_TIME INFINITE
#define MAX_ITERATION 15
#define STEP_LENGTH 80
#define NUM_THREADS 2

#define PULL_POINT_EXIT 0
#define PULL_POINT_SKIP 1
#define PULL_POINT_SUCCESS 2

// Structures
struct ThreadStats
{
	unsigned long numPointsPushed;
	unsigned long numPointsPulled;
	float time;
};

// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name
stack<Point> pointStack;
HANDLE *pThreads;
bool *pThreadStates;
ThreadStats *pThreadStats;
COLORREF *pFractalBitmapData;
int iFractalBitmapWidth;
int iFractalBitmapHeight;
HANDLE hStackSemaphore;
HANDLE hProcessSemaphore;
HANDLE hFractalBitmapSemaphore;

// Forward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);
DWORD WINAPI		ThreadFunction(LPVOID param);
void				RenderFractal(int width, int height);
char				PullPoint(Point &point, int threadIndex);
void				PushPoint(const Point& point);
void				CreateLine(int startX, int startY, int endX, int endY, int iteration);

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: Place code here.
	MSG msg;
	HACCEL hAccelTable;

	// Initialize global variables
	pFractalBitmapData = NULL;
	hStackSemaphore = NULL;
	hFractalBitmapSemaphore = NULL;
	pThreads = NULL;

	// Initialize global strings
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_ZAD1, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_ZAD1));

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	// Remove fractal bitmap data
	if (pFractalBitmapData != NULL)
	{
		delete[] pFractalBitmapData;
		pFractalBitmapData = NULL;
	}

	return (int) msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ZAD1));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_ZAD1);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;
	RECT windowRect;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_RENDER:
			GetWindowRect(hWnd, &windowRect);
			RenderFractal(
				((windowRect.right - windowRect.left) / 4) * 4,
				((windowRect.bottom - windowRect.top) / 4) * 4
			);
			InvalidateRect(hWnd, NULL, FALSE); // wymuszenie wyrysowania całego okna
			break;
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		if (pFractalBitmapData != NULL)
		{
			HDC hdcBitmap = CreateCompatibleDC(hdc);
			HBITMAP hFractalBitmap = CreateBitmap(iFractalBitmapWidth, iFractalBitmapHeight, 1, 32, pFractalBitmapData);
			HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcBitmap, hFractalBitmap);

			BitBlt(hdc, 0, 0, iFractalBitmapWidth, iFractalBitmapHeight, hdcBitmap, 0, 0, SRCCOPY);

			SelectObject(hdcBitmap, hOldBitmap);
			DeleteDC(hdcBitmap);
			DeleteObject(hFractalBitmap);
		}
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

void RenderFractal(int width, int height)
{
	Timer timer;
	string strRaport;
	int iNumThreadsCompleted;
	int *pThreadIndex;

	// Usuwamy dane bitmapy
	if (pFractalBitmapData != NULL)
	{
		delete[] pFractalBitmapData;
		pFractalBitmapData = NULL;
	}

	// Tworzymy dane bitmapy
	pFractalBitmapData = new COLORREF[width*height];
	iFractalBitmapWidth = width;
	iFractalBitmapHeight = height;

	// Stworzenie semaforów
	hStackSemaphore = CreateSemaphore(NULL, 1, 1, NULL);
	hProcessSemaphore = CreateSemaphore(NULL, 0, NUM_THREADS, NULL);
	hFractalBitmapSemaphore = CreateSemaphore(NULL, 1, 1, NULL);

	// Narysowanie tła
	for (int i = 0; i < width*height; i++)
	{
		pFractalBitmapData[i] = RGB(255, 255, 255);
	}

	// Utworzenie punktów początkowych
	int iStartX = width / 2;
	int iStartY = height / 2;

	CreateLine(iStartX, iStartY, iStartX, iStartY - STEP_LENGTH, 0);
	CreateLine(iStartX, iStartY - STEP_LENGTH, iStartX + STEP_LENGTH, iStartY - STEP_LENGTH, 0);
	CreateLine(iStartX, iStartY, iStartX + STEP_LENGTH, iStartY, 0);
	CreateLine(iStartX + STEP_LENGTH, iStartY, iStartX + STEP_LENGTH, iStartY + STEP_LENGTH, 0);
	CreateLine(iStartX, iStartY, iStartX, iStartY + STEP_LENGTH, 0);
	CreateLine(iStartX, iStartY + STEP_LENGTH, iStartX - STEP_LENGTH, iStartY + STEP_LENGTH, 0);
	CreateLine(iStartX, iStartY, iStartX - STEP_LENGTH, iStartY, 0);
	CreateLine(iStartX - STEP_LENGTH, iStartY, iStartX - STEP_LENGTH, iStartY - STEP_LENGTH, 0);

	// Tworzenie wątków
	pThreads = new HANDLE[NUM_THREADS];
	pThreadStates = new bool[NUM_THREADS];
	pThreadStats = new ThreadStats[NUM_THREADS];

	for (int i = 0; i < NUM_THREADS; i++)
	{
		pThreadIndex = new int(i);
		pThreads[i] = CreateThread(
			NULL,
			0,
			ThreadFunction,
			(LPVOID)pThreadIndex,
			CREATE_SUSPENDED,
			NULL
		);
		pThreadStates[i] = false;
		pThreadStats[i].numPointsPulled = 0ul;
		pThreadStats[i].numPointsPushed = 0ul;
		pThreadStats[i].time = 0.0f;
	}

	timer.GetElapsedSeconds();

	// Uruchomienie wątków
	for (int i = 0; i < NUM_THREADS; i++)
	{
		ResumeThread(pThreads[i]);
	}
	
	// Czekamy, aż wątki zakończą działanie
	iNumThreadsCompleted = 0;
	while (iNumThreadsCompleted < NUM_THREADS)
	{
		if (WaitForSingleObject(hProcessSemaphore, POINT_STACK_WAIT_TIME) == WAIT_OBJECT_0)
		{
			iNumThreadsCompleted++;
		}
	}

	// Sporządzenie raportu
	strRaport += string("Czas pracy: ") + floatToStr((float)timer.GetElapsedSeconds()) + string(" sek.");

	for (int i = 0; i < NUM_THREADS; i++)
	{
		strRaport += string("\nWatek #") + intToStr(i);
		strRaport += string(": przetworzone: ") + intToStr(pThreadStats[i].numPointsPulled);
		strRaport += string("; utworzone: ") + intToStr(pThreadStats[i].numPointsPushed);
		strRaport += string("; czas: ") + floatToStr(pThreadStats[i].time) + string(" sek.");
	}

	// Wyświetlenie raportu
	MessageBoxA(0, strRaport.c_str(), "Raport", MB_ICONINFORMATION);

	// Usunięcie listy wątków
	delete[] pThreadStats;
	delete[] pThreadStates;
	delete[] pThreads;

	// Usuniecie semaforów
	CloseHandle(hFractalBitmapSemaphore);
	CloseHandle(hStackSemaphore);
	CloseHandle(hProcessSemaphore);
}

char PullPoint(Point &point, int threadIndex)
{
	DWORD result = WaitForSingleObject(hStackSemaphore, POINT_STACK_WAIT_TIME);
	char pullResult = PULL_POINT_EXIT;
	
	if (result == WAIT_OBJECT_0) 
	{
		if (pointStack.size() > 0) 
		{
			point = pointStack.top();
			pointStack.pop();
			
			pThreadStates[threadIndex] = true;
			pullResult = PULL_POINT_SUCCESS;
		}
		else
		{
			pThreadStates[threadIndex] = false;
			for (int i = 0; i < NUM_THREADS; i++)
			{
				if (pThreadStates[i])
				{
					pullResult = PULL_POINT_SKIP;
				}
			}
		}
		
		ReleaseSemaphore(hStackSemaphore, 1, NULL);
	}

	return pullResult;
}

void PushPoint(const Point& point)
{
	DWORD result = WaitForSingleObject(hStackSemaphore, POINT_STACK_WAIT_TIME);

	if (result == WAIT_OBJECT_0)
	{
		pointStack.push(point);

		ReleaseSemaphore(hStackSemaphore, 1, NULL);
	}
}

void CreateLine(int startX, int startY, int endX, int endY, int iteration)
{
	Point point(endX, endY, iteration);

	// Rysowanie
	if (pFractalBitmapData != NULL)
	{
		if (startX == endX && startY >= endY) // pionowa w górę
		{
			if (WaitForSingleObject(hFractalBitmapSemaphore, POINT_STACK_WAIT_TIME) == WAIT_OBJECT_0)
			{
				for (int y = endY; y <= startY; y++)
				{
					if (endX >= 0
						&& endX < iFractalBitmapWidth
						&& y >= 0
						&& y < iFractalBitmapHeight)
					{
						pFractalBitmapData[y * iFractalBitmapWidth + endX] = RGB(0, 0, 255);
					}
				}

				ReleaseSemaphore(hFractalBitmapSemaphore, 1, NULL);
			}
			point.setDirection(0);
		}
		else if (startY == endY && startX <= endX) // pozioma w prawo
		{
			if (WaitForSingleObject(hFractalBitmapSemaphore, POINT_STACK_WAIT_TIME) == WAIT_OBJECT_0)
			{
				for (int x = startX; x <= endX; x++)
				{
					if (x >= 0
						&& x < iFractalBitmapWidth
						&& endY >= 0
						&& endY < iFractalBitmapHeight)
					{
						pFractalBitmapData[endY * iFractalBitmapWidth + x] = RGB(0, 128, 255);
					}
				}
				ReleaseSemaphore(hFractalBitmapSemaphore, 1, NULL);
			}
			point.setDirection(1);
		}
		else if (startX == endX && startY <= endY) // pionowa w dół
		{
			if (WaitForSingleObject(hFractalBitmapSemaphore, POINT_STACK_WAIT_TIME) == WAIT_OBJECT_0)
			{
				for (int y = startY; y <= endY; y++)
				{
					if (endX >= 0
						&& endX < iFractalBitmapWidth
						&& y >= 0
						&& y < iFractalBitmapHeight)
					{
						pFractalBitmapData[y * iFractalBitmapWidth + endX] = RGB(0, 255, 0);
					}
				}
				ReleaseSemaphore(hFractalBitmapSemaphore, 1, NULL);
			}
			point.setDirection(2);
		}
		else if (startY == endY && startX >= endX) // pozioma w lewo
		{
			if (WaitForSingleObject(hFractalBitmapSemaphore, POINT_STACK_WAIT_TIME) == WAIT_OBJECT_0)
			{
				for (int x = endX; x <= startX; x++)
				{
					if (x >= 0
						&& x < iFractalBitmapWidth
						&& endY >= 0
						&& endY < iFractalBitmapHeight)
					{
						pFractalBitmapData[endY * iFractalBitmapWidth + x] = RGB(255, 0, 0);
					}
				}
				ReleaseSemaphore(hFractalBitmapSemaphore, 1, NULL);
			}
			point.setDirection(3);
		}
	}

	PushPoint(point);
}

DWORD WINAPI ThreadFunction(LPVOID param)
{
	Timer timer;
	int *pThreadIndex = (int*)param;
	Point point;
	int step;
	char chPullPointResult;

	// Dodawanie punktów
	do 
	{
		chPullPointResult = PullPoint(point, *pThreadIndex);

		if (chPullPointResult == PULL_POINT_SUCCESS)
		{
			pThreadStats[*pThreadIndex].numPointsPulled++;

			if (point.getIteration() < MAX_ITERATION)
			{
				step = STEP_LENGTH / (point.getIteration() + 1);
				if (step > 0)
				{
					switch (point.getDirection())
					{
					case 0:
						//W górę
						CreateLine(point.getX(), point.getY(), point.getX(), point.getY() - step, point.getIteration() + 1);
						CreateLine(point.getX(), point.getY() - step, point.getX() + step, point.getY() - step, point.getIteration() + 1);
						break;
					case 1:
						//W prawo
						CreateLine(point.getX(), point.getY(), point.getX() + step, point.getY(), point.getIteration() + 1);
						CreateLine(point.getX() + step, point.getY(), point.getX() + step, point.getY() + step, point.getIteration() + 1);
						break;
					case 2:
						//W dół
						CreateLine(point.getX(), point.getY(), point.getX(), point.getY() + step, point.getIteration() + 1);
						CreateLine(point.getX(), point.getY() + step, point.getX() - step, point.getY() + step, point.getIteration() + 1);
						break;
					case 3:
						//W lewo
						CreateLine(point.getX(), point.getY(), point.getX() - step, point.getY(), point.getIteration() + 1);
						CreateLine(point.getX() - step, point.getY(), point.getX() - step, point.getY() - step, point.getIteration() + 1);
						break;
					}
				}

				pThreadStats[*pThreadIndex].numPointsPushed += 2;
			}
		}
	} while (chPullPointResult != PULL_POINT_EXIT);

	// Czas działania wątku
	pThreadStats[*pThreadIndex].time = timer.GetElapsedSeconds();

	// Ogłoszenie zakończenia wątku
	ReleaseSemaphore(hProcessSemaphore, 1, NULL);

	// Usunięcie indeksu
	delete pThreadIndex;

	return 0;
}